//nclude "tree-pass.h"
#include <cstddef>
#include <cstdio>
#include <string.h>

#include <iostream>
#include <utility>
#include <map>
#include <vector>
// This is the first gcc header to be included

//#include "cfg.h"
#include <gcc-plugin.h>
#include <langhooks.h>
#include <print-tree.h>

//#include "flag-types.h"
#include "literal-to-gprel.hpp"
#include "cast-protect.hpp"
//#include "options.h"
#include "nohp.hpp"
#include "split.hpp"
#include "ifmov.hpp"
#include "force-unroll.hpp"
#include "vect-alias.hpp"
#include "patch-builtin.hpp"
#include "iv-res.hpp"

static constexpr int PLUGIN_BUILTIN_base = 0x600;

enum {
  PLUGIN_BUILTIN_slave_func = PLUGIN_BUILTIN_base,
  PLUGIN_BUILTIN_force_unroll,
  PLUGIN_BUILTIN_max
};

namespace nohp {
  std::vector<nohp_plugin_module*> modules;
  std::vector<nohp_builtin_function*> builtins;
  struct plugin_name_args *plugin_info;
  int current_builtin_code;
  gcc_target targetm_default;
  tree last_builtin;
  void register_module(nohp_plugin_module *module) {
    modules.push_back(module);
  }
  /*像是builtin等等乱七八糟的东西要等合适的时机回调，不然可能会由于GCC还没初始化完全局变量导致错误*/
  void add_builtin(nohp_builtin_function *builtin){
    builtins.push_back(builtin);
    last_builtin = builtin->decl = add_builtin_function(builtin->name, builtin->type(), current_builtin_code++, BUILT_IN_MD, NULL, NULL_TREE);
  }

  static tree builtin_decl(unsigned fcode, bool initialize_p){
    int which = fcode - PLUGIN_BUILTIN_max;
    printf("%d %d\n", fcode, initialize_p);
    if (which >= 0 && which < builtins.size())
      return builtins[fcode - PLUGIN_BUILTIN_max]->decl;
    else
      return targetm_default.builtin_decl(fcode, initialize_p);
  }
  void add_attribute(const attribute_spec *spec) {
    register_attribute(spec);
  }
  static void initialize_attributes(void *, void *){
    for (auto module : modules) {
      module->add_attributes();
    }
  }
  tree fold_builtin(tree fndecl, int n_args, tree *argp, bool ignore){
    //printf("%s\n", IDENTIFIER_POINTER(DECL_NAME(fndecl)));
    unsigned int fcode = DECL_FUNCTION_CODE(fndecl);
    int which = fcode - PLUGIN_BUILTIN_max;
    if (which >= 0 && which < builtins.size()){
      return builtins[which]->fold(fndecl, n_args, argp, ignore);
    } else {
      return targetm_default.fold_builtin(fndecl, n_args, argp, ignore);
    }
  }

  bool gimple_fold_builtin(gimple_stmt_iterator *gsi){
    gimple *stmt = gsi_stmt(*gsi);
    tree fndecl = gimple_call_fndecl(stmt);
    if (fndecl){
      unsigned int fcode = DECL_FUNCTION_CODE(fndecl);
      int which = fcode - PLUGIN_BUILTIN_max;
      if (which >= 0 && which < builtins.size()){
        return builtins[which]->gimple_fold(gsi);
      } else {
        return targetm_default.gimple_fold_builtin(gsi);
      }      
    }
    return false;
  }
  
  rtx expand_builtin(tree exp, rtx target, rtx subtarget, machine_mode mode, int ignore) {
    tree fndecl = TREE_OPERAND(CALL_EXPR_FN(exp), 0);
    unsigned int fcode = DECL_FUNCTION_CODE(fndecl);
    int which = fcode - PLUGIN_BUILTIN_max;
    if (!TREE_READONLY(fndecl)){
      //printf("%s %d %d\n", IDENTIFIER_POINTER(DECL_NAME(fndecl)), DECL_PURE_P(fndecl), TREE_READONLY(fndecl));
    }
    if (which >= 0 && which < builtins.size())
      return builtins[which]->expand(exp, target, subtarget, mode, ignore);
    else if (targetm_default.expand_builtin)
      return targetm_default.expand_builtin(exp, target, subtarget, mode, ignore);
    gcc_unreachable();
  }

  static void initialize_builtins(){
    targetm_default.init_builtins();
    for (auto module : modules){
      module->add_builtins();
    }
    for (auto builtin : builtins){
      builtin->post_decl();
      //module->post_builtins(last_builtin);
    }
    for (auto module : modules){
      module->post_builtins(last_builtin);
    }
  }
  void initialize_modules_and_overrides(){
    for (auto module : modules){
      module->add_passes();
    }
    targetm.expand_builtin = expand_builtin;
    targetm.fold_builtin = fold_builtin;
    targetm.gimple_fold_builtin = gimple_fold_builtin;
    targetm.init_builtins = initialize_builtins;
    targetm.builtin_decl = builtin_decl;
    current_builtin_code = PLUGIN_BUILTIN_max;
    
    register_callback("nohp", PLUGIN_ATTRIBUTES, initialize_attributes, nullptr);
  }
  
  void register_pass(opt_pass *pass, const char *ref, int refnum, enum pass_positioning_ops op) {
    struct register_pass_info info;
    info.pass = pass;
    info.reference_pass_name = ref;
    info.ref_pass_instance_number = refnum;
    info.pos_op = op;
    register_callback(plugin_info->base_name, PLUGIN_PASS_MANAGER_SETUP, NULL, &info);
  }

};
static struct plugin_info my_gcc_plugin_info =
  { "1.0", "pick host or slave function to compile" };


int plugin_is_GPL_compatible;

void run_pass(void *, void *){
  puts(current_pass->name);
}
#include <toplev.h>
int plugin_init (struct plugin_name_args *plugin_info,
	     struct plugin_gcc_version *version)
{
  register_callback(plugin_info->base_name,
                    /* event */ PLUGIN_INFO,
                    /* callback */ NULL,
                    /* user_data */
                    &my_gcc_plugin_info);
  nohp::targetm_default = targetm;
  nohp::plugin_info = plugin_info;
  nohp::register_module(new sw_64_literal_to_gprel());
  nohp::register_module(new sw_64_cast_protect());
  
  nohp::register_module(new sw_64_split());
  //nohp::register_module(new sw_64_hybrid());
  nohp::register_module(new sw_64_slave_ifmov());
  nohp::register_module(new sw_64_force_unroll());
  nohp::register_module(new sw_64_vect_alias());
  nohp::register_module(new sw_64_patch_builtins());
  nohp::register_module(new sw_64_iv_res());
  nohp::initialize_modules_and_overrides();
  //register_callback("nohp", PLUGIN_PASS_EXECUTION, run_pass, NULL);
  return 0;
}
